package com.zws.cucumber.expr;

import com.zws.cucumber.expr.unit.ZwsExprUnitActualTransformationImpl;
import com.zws.cucumber.expr.unit.ZwsExprUnitImpl;
import com.zws.cucumber.util.colorfullog.ZwsColorfulLogger;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.slf4j.Logger;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * author: zws
 */
public class ZwsExpr {

    private static final Logger logger = ZwsColorfulLogger.of(ZwsExpr.class);

    public static final String SYMBOL_BRACKETS_LEFT = "[";
    public static final String SYMBOL_BRACKETS_RIGHT = "]";
    public static final String SYMBOL_PARENTHESE_LEFT = "(";
    public static final String SYMBOL_PARENTHESE_RIGHT = ")";
    public static final String SYMBOL_COLON = ":";
    public static final String SYMBOL_COMMA = ",";

    private static final ConcurrentMap<String, ZwsExprUnit> exprMapping = new ConcurrentHashMap<>();
    private static final ConcurrentMap<ZwsExprMode, ZwsExprUnit> defaultExprMapping = new ConcurrentHashMap<>();

    static {
        ZwsExprBuiltInGenerators.init();
        ZwsExprBuiltInTransformers.init();
        ZwsExprBuiltInVerifiers.init();
    }

    public static void registerExpr(String name, Set<String> aliases, ZwsExprMode mode, boolean isDefault, ZwsExprFunc func) {
        ZwsExprUnit unit = new ZwsExprUnitImpl(name, aliases, mode, func);
        exprMapping.put(name.toLowerCase(), unit);
        for (String alias : aliases) {
            exprMapping.put(alias.toLowerCase(), unit);
        }
        if (isDefault) {
            ZwsExprUnit prev = defaultExprMapping.putIfAbsent(mode, unit);
            if (prev != null) {
                throw new IllegalArgumentException("ZwsExprUnit is existed for " + mode);
            }
        }
    }

    /*
     * generator
     */
    public static void registerGenerator(String name, Set<String> aliases, boolean isDefault, ZwsExprFunc func) {
        registerExpr(name, aliases, ZwsExprMode.GENERATOR, isDefault, func);
    }

    public static void registerGenerator(String name, Set<String> aliases, ZwsExprFunc func) {
        registerGenerator(name, aliases, false, func);
    }

    public static void registerGenerator(String name, ZwsExprFunc func) {
        registerGenerator(name, new HashSet<>(), func);
    }

    public static void registerGenerator(String name, Set<String> aliases, boolean isDefault, ZwsExprFuncWithoutCtx func) {
        registerExpr(name, aliases, ZwsExprMode.GENERATOR, isDefault, (ctx, args, pattern, actual) -> func.apply(args, pattern, actual));
    }

    public static void registerGenerator(String name, Set<String> aliases, ZwsExprFuncWithoutCtx func) {
        registerGenerator(name, aliases, false, func);
    }

    public static void registerGenerator(String name, ZwsExprFuncWithoutCtx func) {
        registerGenerator(name, new HashSet<>(), func);
    }

    /*
     * transformer
     */
    public static void registerTransformer(String name, Set<String> aliases, boolean isDefault, ZwsExprFunc func) {
        registerExpr(name, aliases, ZwsExprMode.TRANSFORMER, isDefault, func);
    }

    public static void registerTransformer(String name, Set<String> aliases, ZwsExprFunc func) {
        registerTransformer(name, aliases, false, func);
    }

    public static void registerTransformer(String name, ZwsExprFunc func) {
        registerTransformer(name, new HashSet<>(), func);
    }

    public static void registerTransformer(String name, Set<String> aliases, boolean isDefault, ZwsExprFuncWithoutCtx func) {
        registerExpr(name, aliases, ZwsExprMode.TRANSFORMER, isDefault, (ctx, args, pattern, actual) -> func.apply(args, pattern, actual));
    }

    public static void registerTransformer(String name, Set<String> aliases, ZwsExprFuncWithoutCtx func) {
        registerTransformer(name, aliases, false, func);
    }

    public static void registerTransformer(String name, ZwsExprFuncWithoutCtx func) {
        registerTransformer(name, new HashSet<>(), func);
    }

    /*
     * verifier
     */
    public static void registerVerifier(String name, Set<String> aliases, boolean isDefault, ZwsExprFunc func) {
        registerExpr(name, aliases, ZwsExprMode.VERIFIER, isDefault, (ctx, args, pattern, actual) -> {
            ctx.setExpected(pattern);
            ctx.setActual(actual);
            return func.apply(ctx, args, pattern, actual);
        });
    }

    public static void registerVerifier(String name, Set<String> aliases, ZwsExprFunc func) {
        registerVerifier(name, aliases, false, func);
    }

    public static void registerVerifier(String name, ZwsExprFunc func) {
        registerVerifier(name, new HashSet<>(), func);
    }

    public static void registerVerifier(String name, Set<String> aliases, boolean isDefault, ZwsExprFuncWithoutCtx func) {
        registerExpr(name, aliases, ZwsExprMode.VERIFIER, isDefault, (ctx, args, pattern, actual) -> {
            ctx.setExpected(pattern);
            ctx.setActual(actual);
            return func.apply(args, pattern, actual);
        });
    }

    public static void registerVerifier(String name, Set<String> aliases, ZwsExprFuncWithoutCtx func) {
        registerVerifier(name, aliases, false, func);
    }

    public static void registerVerifier(String name, ZwsExprFuncWithoutCtx func) {
        registerVerifier(name, new HashSet<>(), func);
    }

    public static Set<String> aliases(String ... aliases) {
        return Stream.of(aliases)
            .collect(Collectors.toSet());
    }

    public static List<Triple<ZwsExprUnit, List<String>, String>> parse(String raw, ZwsExprMode mode) {

        List<Triple<ZwsExprUnit, List<String>, String>> list = new ArrayList<>();
        if (raw == null) {
            return list;
        }

        raw = parseActualPart(raw, list);
        raw = parsePatternPart(raw, list);

        if (list.size() == 0) {
            list.add(Triple.of(getDefaultExprUnit(mode), new ArrayList<>(), raw));
        }
        if (list.get(list.size() - 1).getLeft().mode() != mode) {
            list.add(Triple.of(getDefaultExprUnit(mode), new ArrayList<>(), null));
        }
        return list;
    }

    private static String parseActualPart(String raw, List<Triple<ZwsExprUnit, List<String>, String>> list) {
        if (raw.startsWith(SYMBOL_PARENTHESE_LEFT)) {
            do {
                List<String> args = new ArrayList<>();
                int endParenthesesIndex = raw.indexOf(SYMBOL_PARENTHESE_RIGHT);
                String expr = raw.substring(1, endParenthesesIndex);
                String[] typeAndArgs = expr.split(SYMBOL_COLON);
                ZwsExprUnit unit = getExprUnit(typeAndArgs[0].trim());
                if (unit == null) {
                    break;
                }
                if (unit.mode() != ZwsExprMode.TRANSFORMER) {
                    throw new IllegalArgumentException(typeAndArgs[0].trim());
                }
                if (typeAndArgs.length > 1) {
                    args = Arrays.stream(typeAndArgs[1].split(SYMBOL_COMMA)).map(String::trim).collect(Collectors.toList());
                }
                list.add(Triple.of(new ZwsExprUnitActualTransformationImpl(unit), args, null));
                raw = endParenthesesIndex >= raw.length() - 1
                    ? null
                    : raw.substring(endParenthesesIndex + 1).trim();
            } while (raw != null && raw.startsWith(SYMBOL_PARENTHESE_RIGHT));
        }
        return raw;
    }

    private static String parsePatternPart(String raw, List<Triple<ZwsExprUnit, List<String>, String>> list) {
        if (raw.startsWith(SYMBOL_BRACKETS_LEFT)) {
            do {
                int endBracketsIndex = raw.indexOf(SYMBOL_BRACKETS_RIGHT);
                String expr = raw.substring(1, endBracketsIndex);
                List<String> args = new ArrayList<>();
                String[] typeAndArgs = expr.split(SYMBOL_COLON);
                ZwsExprUnit unit = getExprUnit(typeAndArgs[0].trim());
                if (unit == null) {
                    break;
                }
                if (typeAndArgs.length > 1) {
                    args = Arrays.stream(typeAndArgs[1].split(SYMBOL_COMMA)).map(String::trim).collect(Collectors.toList());
                }
                list.add(Triple.of(unit, args, null));

                raw = endBracketsIndex >= raw.length() - 1
                    ? null
                    : raw.substring(endBracketsIndex + 1).trim();
            } while(raw != null && raw.startsWith(SYMBOL_BRACKETS_LEFT));
            // rewrite the 1st rule, the rest of raw is pattern
            if (list.size() > 0) {
                Triple<ZwsExprUnit, List<String>, String> firstExpr = list.get(0);
                list.set(0, Triple.of(firstExpr.getLeft(), firstExpr.getMiddle(), raw));
            }
        }
        return raw;
    }

    private static ZwsExprUnit getExprUnit(String type) {
        return exprMapping.get(type.toLowerCase());
    }

    private static ZwsExprUnit getDefaultExprUnit(ZwsExprMode mode) {
        return defaultExprMapping.get(mode);
    }

    public static Object execute(String pipeline, List<Triple<ZwsExprUnit, List<String>, String>> list, Object actual, ZwsExprCtx ctx) {
        Pair<Object, Object> result = null;
        Object actual0 = null;
        Object output = null;
        if (list.size() > 1) {
            logger.debug("\n<c>-------------------------------- pipeline: {}</c>", pipeline);
        }
        for (Triple<ZwsExprUnit, List<String>, String> unit : list) {
            String input = unit.getRight();
            if (input == null) {
                unit = Triple.of(unit.getLeft(), unit.getMiddle(), input = (String) output);
            }
            try {
                result = unit.getLeft().func().apply(ctx, unit.getMiddle(), input, actual);
                output = result.getLeft();
                actual0 = result.getRight();
            } finally {
                if (list.size() > 1) {
                    if (unit.getLeft() instanceof ZwsExprUnitActualTransformationImpl) {
                        logger.debug("pipeline <y>{}</y>, <b>input [</b>{}<b>], output {}:[</b>{}<b>]</b>",
                                unit.getLeft().toString(),
                                actual,
                                output == null ? "null" : output.getClass().getSimpleName(), actual0);
                    } else {
                        logger.debug("pipeline <y>{}</y>, <b>input [</b>{}<b>], output {}:[</b>{}<b>]</b>",
                                unit.getLeft().toString(),
                                input,
                                output == null ? "null" : output.getClass().getSimpleName(),
                                output);
                    }
                }
                actual = actual0;
            }
        }
        if (list.size() > 1) {
            logger.debug("\n<c>----------------------------------------------------------------</c>");
        }
        return output;
    }

    public static Object execute(String pipeline, List<Triple<ZwsExprUnit, List<String>, String>> list, Object actual) {
        return execute(pipeline, list, actual, new ZwsExprCtx());
    }

    public static <T> T evaluate(String name, String expr, ZwsExprCtx ctx) {
        T result = null;
        try {
            return result = (T) execute(expr, parse(expr, ZwsExprMode.GENERATOR), null, ctx);
        } finally {
            if (!Objects.equals(expr, result)) {
                logger.debug("evaluate <y>{}</y>, <b>[</b>{}<b>] = {}:[</b>{}<b>]</b>",
                        name,
                        expr,
                        result == null ? "null" : result.getClass().getSimpleName(),
                        result);
            }
        }
    }

    public static <T> T evaluate(String name, String expr) {
        return evaluate(name, expr, new ZwsExprCtx());
    }

    public static <T> T evaluateSilently(String name, String expr, ZwsExprCtx ctx) {
        try {
            return evaluate(name, expr, ctx);
        } catch (Throwable t) {
            return (T) expr;
        }
    }
    public static <T> T evaluateSilently(String name, String expr) {
        return evaluateSilently(name, expr, new ZwsExprCtx());
    }

    public static void verify(String key, String expectedRawValue, Object actualValue, ZwsExprCtx ctx) {
        List<Triple<ZwsExprUnit, List<String>, String>> pipeline = null;
        Triple<ZwsExprUnit, List<String>, String> lastExprTuple = null;
        Function<List<Triple<ZwsExprUnit, List<String>, String>>, ZwsExprUnit> getExprType = (p) ->
            CollectionUtils.isEmpty(p) ? null : p.get(p.size() - 1).getLeft();
        try {
            execute(expectedRawValue, pipeline = parse(expectedRawValue, ZwsExprMode.VERIFIER), actualValue, ctx);
            logger.debug("verify <y>{}</y> with {}, <g>true</g>, <b>E {}:[</b>{}<b>]</b>, <b>A {}:[</b>{}<b>]</b>, <b>RAW E String:[</b>{}<b>]</b>, <b>RAW A {}:[</b>{}<b>]</b>",
                    key,
                    getExprType.apply(pipeline),
                    ctx.getExpected() == null ? "null" : ctx.getExpected().getClass().getSimpleName(),
                    ctx.getExpected(),
                    ctx.getActual() == null ? "null" : ctx.getActual().getClass().getSimpleName(),
                    ctx.getActual(),
                    expectedRawValue,
                    actualValue == null ? "null" : actualValue.getClass().getSimpleName(),
                    actualValue);
        } catch (Throwable e) {
            try {
                logger.debug("verify <y>{}</y> with {}, <r>false</r>, <b>E {}:[</b>{}<b>]</b>, <b>A {}:[</b>{}<b>]</b>, <b>RAW E String:[</b>{}<b>]</b>, <b>RAW A {}:[</b>{}<b>]</b>",
                        key,
                        getExprType.apply(pipeline),
                        ctx.getExpected() == null ? "null" : ctx.getExpected().getClass().getSimpleName(),
                        ctx.getExpected(),
                        ctx.getActual() == null ? "null" : ctx.getActual().getClass().getSimpleName(),
                        ctx.getActual(),
                        expectedRawValue,
                        actualValue == null ? "null" : actualValue.getClass().getSimpleName(),
                        actualValue);
            } catch (Throwable ex) {
                logger.debug("", ex);
            }
            throw e;
        }
    }

    public static void verify(String key, String expectedRawValue, Object actualValue) {
        verify(key, expectedRawValue, actualValue, new ZwsExprCtx());
    }

    public static boolean verifySilently(String key, String expectedRawValue, Object actualValue, ZwsExprCtx ctx) {
        try {
            verify(key, expectedRawValue, actualValue, ctx);
            return true;
        } catch (AssertionError e) {
            return false;
        }
    }

    public static boolean verifySilently(String key, String expectedRawValue, Object actualValue) {
        return verifySilently(key, expectedRawValue, actualValue, new ZwsExprCtx());
    }

}
